﻿using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using TFSWIMorphying.GeneralUtilities;
using TFSWIMorphying.DataModel;
using TFSWIMorphying.DataStructure;

namespace TFSWIMorphying
{
    //TODO: Support for Parameters and input for XML file.
    //TODO: XSD validation.
    //TODO: Entity Model for rules - Convert XML file input into Objects.
    //TODO: Log file creation.
    //TODO: Error Handling
    //TODO: Unit Testing.
    //TODO: Divide programme in appropriate modules and components.
    //TODO: Support for Globalization and Localization.
    //TODO: [FEATURE] Support for AGGREGATION of data in a given field.
    //TODO: [FEATURE] Support for WorkItemQueryLanguage as Parameter.
    //TODO: [FEATURE] Dry run the application so that it does not make any changes but performs validation only.
    //TODO: [FEATURE] Handle HTML and PlainText fields effectively. This will be important for Description, Definition of Done, Acceptance Criteria etc. 
    class Program
    {
        static void Main(string[] args)
        {
            ArgumentParserHelper CommandLineArgs = new ArgumentParserHelper(args);

            if (CommandLineArgs["TFSWIMorphyingConfig"] != null)
            {
                PerformTFSWorkItemDataMigration(CommandLineArgs["TFSWIMorphyingConfig"]);
            }
            else ShowTFSWIMorphyingUsage();
        }

        private static void PerformTFSWorkItemDataMigration(string tfsWIMorphyingConfig)
        {
            using (Stream stream = Assembly.GetCallingAssembly().GetManifestResourceStream("TFSWIMorphying.DataModel.TFSWIMorphyingConfig.xsd"))
            {
                WriteLineSeparator();
                DateTime _startTime = DateTime.Now;
                Console.WriteLine("Start Time DD/MM/YYYY   : {0}" , _startTime.ToString("dd/MM/yyyy HH:mm:ss"));
                WriteLineSeparator();
                Console.WriteLine("TFSWIMorphyingConfig    : " + tfsWIMorphyingConfig);

                bool isValid = false;
                try
                {
                    isValid = XML_XSD_Validator.Validate(tfsWIMorphyingConfig, stream);
                }
                catch (XmlException xmlex)
                {
                    Console.WriteLine("ERROR                  : " + xmlex.Message);
                    WriteLineSeparator();
                    return;
                }

                TFSWorkItemDataMigrationRules _tfsWIMorphyingConfig = new TFSWorkItemDataMigrationRules();
                bool isTFSWIMorphyingConfigLoaded;

                isTFSWIMorphyingConfigLoaded = GenericXmlSerializer<TFSWorkItemDataMigrationRules>.LoadFromFile(tfsWIMorphyingConfig, out _tfsWIMorphyingConfig);


                if (isTFSWIMorphyingConfigLoaded)
                {
                    Console.WriteLine("TFS Server Path         : " + _tfsWIMorphyingConfig.ConnectionDetails.CollectionURL);
                    foreach (var TFSProject in _tfsWIMorphyingConfig.ConnectionDetails.Projects)
                    {
                        Console.WriteLine("    TFS Project         : " + TFSProject.ToString());
                    }
                    if (_tfsWIMorphyingConfig.ConnectionDetails.Credentials.UserID != "")
                        Console.WriteLine("    User ID                 : " + _tfsWIMorphyingConfig.ConnectionDetails.Credentials.UserID);
                    foreach (TFSWIMorphying.DataModel.WorkItemType _workItemType in _tfsWIMorphyingConfig.WorkItemTypes)
                    {
                        Console.WriteLine("    WorkItem Type       : " + _workItemType.Name);
                        if (_workItemType.FieldMap.MappedFields.Count > 0)
                        {
                            Console.WriteLine("    FieldMap            :  ");

                            foreach (TFSWIMorphying.DataModel.MappedField _mappedField in _workItemType.FieldMap.MappedFields)
                            {
                                Console.Write("        MappedField     :  LeftName - " + _mappedField.LeftName +
                                    " RightName - " + _mappedField.RightName);
                                if (_mappedField.ValueMap != null)
                                {
                                    Console.WriteLine(" ValueMap - " + _mappedField.ValueMap);
                                }
                                else
                                {
                                    Console.WriteLine("");
                                }
                            }
                        }
                        if (_workItemType.ValueMaps.Count > 0)
                        {
                            foreach (TFSWIMorphying.DataModel.ValueMap _valueMap in _workItemType.ValueMaps)
                            {
                                Console.WriteLine("        ValueMap        : {0} ", _valueMap.Name);
                                foreach (TFSWIMorphying.DataModel.Value _value in _valueMap.Values)
                                {
                                    Console.WriteLine("            Value       :     LeftValue - " + _value.LeftValue +
                                        " RightValue - " + _value.RightValue);
                                }
                            }
                        }
                        if (_workItemType.LinkTypeMappings.Count > 0)
                        {
                            foreach (TFSWIMorphying.DataModel.LinkTypeMapping _linkTypeMappings in _workItemType.LinkTypeMappings)
                            {
                                Console.WriteLine("        LinkTypeMapping : LeftLinkType - " + _linkTypeMappings.LeftLinkType +
                                        " RightLinkType - " + _linkTypeMappings.RightLinkType);
                            }
                            Console.WriteLine("");
                        }
                    }


                    //TODO: Validate Config File based on following list.
                    //      1. Check Field Type for migration and raise error if it's not valid migration field. e.g. we can't migrate from Text to Date field. 
                    //      2. For LinkType, check that there are no Circular references to the same LinkType within a given WorkItem Type.
                    //      3. Validate Look-up List values for ValueType Items.
                    //      4. 
                    WriteLineSeparator();

                    //var tfsCollection =
                    //    TfsTeamProjectCollectionFactory.GetTeamProjectCollection(
                    //        new Uri("http://tfsserver:8080/tfs/DefaultCollection"));
                    var tfsCollection =
                        TfsTeamProjectCollectionFactory.GetTeamProjectCollection(
                            new Uri(_tfsWIMorphyingConfig.ConnectionDetails.CollectionURL));
                    WorkItemStore workItemStore = tfsCollection.GetService<WorkItemStore>();



                    foreach (var ProjectNameFromConfigFile in _tfsWIMorphyingConfig.ConnectionDetails.Projects)
                    {
                        foreach (var project in tfsCollection.GetService<VersionControlServer>().GetAllTeamProjects(false))
                        {
                            if (String.Compare(project.Name, ProjectNameFromConfigFile, StringComparison.OrdinalIgnoreCase) == 0)
                            {
                                System.Console.WriteLine("Found project - {0}", ProjectNameFromConfigFile);
                                foreach (TFSWIMorphying.DataModel.WorkItemType _workItemType in _tfsWIMorphyingConfig.WorkItemTypes)
                                {
                                    WorkItemCollection queryResults = workItemStore.Query("SELECT [System.Id], [System.WorkItemType], [System.Title],[System.State] FROM WorkItems WHERE [System.TeamProject] = '" + ProjectNameFromConfigFile + "' and [System.WorkItemType] = '" + _workItemType.Name + "' ORDER BY [System.Id]");
                                    //WorkItemCollection queryResults = workItemStore.Query("SELECT [System.Id], [System.WorkItemType], [System.Title],[System.State] FROM WorkItems WHERE [System.TeamProject] = '" + ProjectNameFromConfigFile + 
                                    //    "' and [System.WorkItemType] = '" + _workItemType.Name +
                                    //    "' and [System.Id] > " + "203367" +
                                    //    " ORDER BY [System.Id]");
                                    Console.WriteLine("");
                                    Console.WriteLine("Processing \"{0}\" WorkItem Type...", _workItemType.Name);
                                    foreach (WorkItem workItem in queryResults)
                                    {
                                        Console.WriteLine("    ID         : {0} ", workItem.Id);
                                        Console.WriteLine("    Type       : {0} ", workItem.Type.Name);
                                        Console.WriteLine("    Title      : {0} ", workItem.Title);
                                        workItem.Open();

                                        foreach (TFSWIMorphying.DataModel.MappedField _mappedField in _workItemType.FieldMap.MappedFields)
                                        {
                                            foreach (Field field in workItem.Fields)
                                            {
                                                if (String.Compare(field.ReferenceName, _mappedField.RightName,
                                                    StringComparison.OrdinalIgnoreCase) == 0)
                                                {
                                                    if (_mappedField.ValueMap != null)
                                                    {
                                                        // TODO: Take care of ValueMap Console.WriteLine(" ValueMap - " + _mappedField.ValueMap);
                                                        Boolean _foundValueMap = false;
                                                        if (_workItemType.ValueMaps.Count > 0)
                                                        {
                                                            foreach (TFSWIMorphying.DataModel.ValueMap _valueMap in _workItemType.ValueMaps)
                                                            {
                                                                if (String.Compare(_valueMap.Name, _mappedField.ValueMap,
                                                                    StringComparison.OrdinalIgnoreCase) == 0)
                                                                {
                                                                    _foundValueMap = true;
                                                                    Dictionary<string, string> _valueMapDirectory = new Dictionary<string, string>();

                                                                    foreach (TFSWIMorphying.DataModel.Value _value in _valueMap.Values)
                                                                    {
                                                                        _valueMapDirectory.Add(_value.LeftValue, _value.RightValue);

                                                                    }

                                                                    if (_valueMapDirectory.ContainsKey(workItem.Fields[_mappedField.LeftName].Value.ToString()))
                                                                    {
                                                                        Console.WriteLine("        Field Update  : Field Name - {0} From Value - \"{1}\" To Value = \"{2}\" ",
                                                                            workItem.Fields[_mappedField.RightName].ReferenceName, workItem.Fields[_mappedField.RightName].Value, _valueMapDirectory[workItem.Fields[_mappedField.LeftName].Value.ToString()]);
                                                                        workItem.Fields[_mappedField.RightName].Value = _valueMapDirectory[workItem.Fields[_mappedField.LeftName].Value.ToString()];

                                                                    }
                                                                }
                                                            }
                                                        }

                                                    }
                                                    else
                                                    {
                                                        if (field.Value != null && workItem.Fields[_mappedField.LeftName].Value != null)
                                                        {
                                                            if (workItem.Fields[_mappedField.LeftName].Value.ToString().Length < 20)
                                                            {
                                                                if (field.Value.ToString().Length < 20)
                                                                {

                                                                    Console.WriteLine("        Field Update  : Field Name - {0} From Value - \"{1}\" To Value = \"{2}\" ",
                                                                       field.ReferenceName,
                                                                       field.Value.ToString(),
                                                                       workItem.Fields[_mappedField.LeftName].Value.ToString());
                                                                }
                                                                else
                                                                {
                                                                    Console.WriteLine("        Field Update  : Field Name - {0} From Value - \"{1}\" To Value = \"{2}\" ",
                                                                        field.ReferenceName,
                                                                        field.Value.ToString().Substring(0, 19) + "...",
                                                                        workItem.Fields[_mappedField.LeftName].Value.ToString());
                                                                }
                                                            }
                                                            else
                                                            {
                                                                if (field.Value.ToString().Length < 20)
                                                                {
                                                                    Console.WriteLine("        Field Update  : Field Name - {0} From Value - \"{1}\" To Value = \"{2}\" ",
                                                                        field.ReferenceName,
                                                                        field.Value.ToString(),
                                                                        workItem.Fields[_mappedField.LeftName].Value.ToString().Substring(0, 19) + "...");
                                                                }
                                                                else
                                                                {
                                                                    Console.WriteLine("        Field Update  : Field Name - {0} From Value - \"{1}\" To Value = \"{2}\" ",
                                                                        field.ReferenceName,
                                                                        field.Value.ToString().Substring(0, 19) + "...",
                                                                        workItem.Fields[_mappedField.LeftName].Value.ToString().Substring(0, 19) + "...");
                                                                }
                                                            }
                                                        }
                                                        else
                                                        {
                                                            Console.WriteLine("        Field Update  : Field Name - {0}", field.ReferenceName);
                                                        }
                                                        //TODO: If Source field value is a MultiLine Text box then target field loses Line Feed
                                                        //      characters while copying value from source to target. Due to this everything is
                                                        //      cluttered as one large paragraph. 
                                                        field.Value = workItem.Fields[_mappedField.LeftName].Value;
                                                    }

                                                }

                                            }
                                        }

                                        if (_workItemType.LinkTypeMappings.Count > 0)
                                        {
                                            foreach (TFSWIMorphying.DataModel.LinkTypeMapping _linkTypeMappings in _workItemType.LinkTypeMappings)
                                            {
                                                WorkItemLinkType rightLinkType = workItemStore.WorkItemLinkTypes[_linkTypeMappings.RightLinkType];
                                                WorkItemLinkTypeEnd rightLinkTypeEnd = workItemStore.WorkItemLinkTypes.LinkTypeEnds[rightLinkType.ForwardEnd.Name];
                                                WorkItemLinkType leftLInkType = workItemStore.WorkItemLinkTypes[_linkTypeMappings.LeftLinkType];
                                                WorkItemLinkTypeEnd leftLinkTypeEnd = workItemStore.WorkItemLinkTypes.LinkTypeEnds[leftLInkType.ForwardEnd.Name];

                                                List<Link> linksToBeRemoved = new List<Link>();
                                                if (workItem.Links.Count > 0)
                                                {
                                                    List<LinkInfoData> linkList = new List<LinkInfoData>();

                                                    foreach (Link link in workItem.Links)
                                                    {
                                                        if (link.BaseType == BaseLinkType.RelatedLink)
                                                        {
                                                            if (String.Compare(_linkTypeMappings.LeftLinkType,
                                                                ((RelatedLink)link).LinkTypeEnd.LinkType.ReferenceName,
                                                                StringComparison.OrdinalIgnoreCase) == 0)
                                                            {
                                                                linksToBeRemoved.Add(link);
                                                                if (((RelatedLink)link).LinkTypeEnd.IsForwardLink == true)
                                                                {
                                                                    linkList.Add(new LinkInfoData(rightLinkTypeEnd,
                                                                        ((RelatedLink)link).RelatedWorkItemId,
                                                                        workItem.Id));
                                                                    //TODO: update link type.
                                                                }
                                                                else
                                                                {
                                                                    linkList.Add(new LinkInfoData(rightLinkTypeEnd, workItem.Id,
                                                                        ((RelatedLink)link).RelatedWorkItemId));
                                                                }
                                                            }
                                                        }
                                                    }
                                                    foreach (LinkInfoData linkTypeEnd in linkList)
                                                    {

                                                        if (workItem.Id == linkTypeEnd.FromWorkItemID)
                                                        {
                                                            RelatedLink newRelatedLink = new RelatedLink(linkTypeEnd.workItemLinkTypeEnd,
                                                                                                         linkTypeEnd.ToWorkItemID);
                                                            try
                                                            {
                                                                workItem.Links.Add(newRelatedLink);
                                                                Console.WriteLine("        Link Added    : Link ID - {0} Link Type - {1}", newRelatedLink.RelatedWorkItemId, ((RelatedLink)newRelatedLink).LinkTypeEnd.LinkType.ReferenceName);
                                                            }
                                                            catch (Microsoft.TeamFoundation.WorkItemTracking.Client.ValidationException WIValidationException)
                                                            {
                                                                // TODO - Handle TF237099: Duplicate work item link.
                                                                // IGNORE if WorkItem Link is already present.
                                                                Console.WriteLine("****WARNING WorkItem ID {0} already has link to WorkItem ID {1} via LinkType {2}", workItem.Id, linkTypeEnd.ToWorkItemID, rightLinkType.ReferenceName);

                                                            }
                                                            catch (Exception ex)
                                                            {
                                                                Console.WriteLine("****ERROR OCCURED while saving WorkItem in TFS");
                                                                Console.WriteLine("    Exception Message: {0}", ex.Message);
                                                                Console.WriteLine("    Exception Staketrace : {0}", ex.StackTrace.ToString());
                                                            }
                                                        }
                                                        else
                                                        {
                                                            WorkItem parentWorkItem = workItemStore.GetWorkItem(linkTypeEnd.FromWorkItemID);
                                                            RelatedLink newRelatedLink = new RelatedLink(linkTypeEnd.workItemLinkTypeEnd,
                                                                                                         linkTypeEnd.ToWorkItemID);
                                                            try
                                                            {

                                                                parentWorkItem.Links.Add(newRelatedLink);
                                                                Console.WriteLine("        Link Added    : Link ID - {0} Link Type - {1}", newRelatedLink.RelatedWorkItemId, ((RelatedLink)newRelatedLink).LinkTypeEnd.LinkType.ReferenceName);
                                                            }
                                                            catch (Microsoft.TeamFoundation.WorkItemTracking.Client.ValidationException WIValidationException)
                                                            {
                                                                // TODO - Handle TF237099: Duplicate work item link.
                                                                // IGNORE if WorkItem Link is already present.
                                                                Console.WriteLine("****WARNING WorkItem ID {0} already has link to WorkItem ID {1} via LinkType {2}", parentWorkItem.Id, linkTypeEnd.ToWorkItemID, rightLinkType.ReferenceName);
                                                            }
                                                            try
                                                            {
                                                                // Validate the WorkItem before saving it. 
                                                                var parentWorkItemFailedValidationFieldList = parentWorkItem.Validate();
                                                                if (parentWorkItemFailedValidationFieldList.Count > 0)
                                                                {
                                                                    foreach (var failedValidationField in parentWorkItemFailedValidationFieldList)
                                                                    {
                                                                        Console.WriteLine("****ERROR WorkItem ID {0} can not be saved because it has invalid data." +
                                                                            " Tried to save it with following values ", parentWorkItem.Id.ToString());
                                                                        Console.WriteLine("       {0} - {1}", ((Field)failedValidationField).ReferenceName, ((Field)failedValidationField).Value);
                                                                    }
                                                                    // No need to close WorkItem as it's going to happen in Finally block automatically.
                                                                }
                                                                else
                                                                {
                                                                    parentWorkItem.Save();
                                                                }
                                                            }
                                                            catch (Exception ex)
                                                            {
                                                                Console.WriteLine("****ERROR OCCURED while saving Parent WorkItem {0} in TFS",parentWorkItem.Id.ToString());
                                                                Console.WriteLine("    Exception Message: {0}", ex.Message);
                                                                Console.WriteLine("    Exception Staketrace : {0}", ex.StackTrace.ToString());
                                                            }
                                                            finally
                                                            {
                                                                parentWorkItem.Close();
                                                            }

                                                        }
                                                    }

                                                    foreach (Link link in linksToBeRemoved)
                                                    {
                                                        Console.WriteLine("        Link Deleted  : Link ID - {0} Link Type - {1}", ((RelatedLink)link).RelatedWorkItemId, ((RelatedLink)link).LinkTypeEnd.LinkType.ReferenceName);
                                                        workItem.Links.Remove(link);
                                                    }
                                                }
                                            }
                                        }

                                        // Validate the WorkItem before saving it. 
                                        var failedValidationFieldList = workItem.Validate();

                                        try
                                        {
                                            if (failedValidationFieldList.Count > 0)
                                            {
                                                foreach (var failedValidationField in failedValidationFieldList)
                                                {
                                                    Console.WriteLine("****ERROR WorkItem ID {0} can not be saved because it has invalid data." +
                                                        " Tried to save it with following values ", workItem.Id.ToString());
                                                    Console.WriteLine("       {0} - {1}", ((Field)failedValidationField).ReferenceName, ((Field)failedValidationField).Value);
                                                }
                                                // No need to close WorkItem as it's going to happen in Finally block automatically.
                                            }
                                            else
                                            {
                                                workItem.Save();
                                            }
                                        }
                                        catch (Exception ex)
                                        {
                                            Console.WriteLine("****ERROR OCCURED while saving WorkItem in TFS");
                                            Console.WriteLine("    Exception Message: {0}", ex.Message);
                                            Console.WriteLine("    Exception Staketrace : {0}", ex.StackTrace.ToString());
                                            Console.WriteLine("****ERROR WorkItem ID {0} can not be saved because it has invalid data." +
                                                " Tried to save it with following values ", workItem.Id.ToString());
                                            foreach (Field field in workItem.Fields)
                                            {
                                                Console.WriteLine("       {0} - {1}", field.ReferenceName, field.Value);
                                            }
                                        }
                                        finally
                                        {
                                            workItem.Close();
                                            Console.WriteLine("");
                                        }
                                    }
                                    System.Console.WriteLine("Total {0} {1} found", queryResults.Count, _workItemType.Name);
                                }
                            }
                        }
                    }
                }
                DateTime _endTime = DateTime.Now;
                WriteLineSeparator();
                Console.WriteLine("End Time DD/MM/YYYY     : {0}", _endTime.ToString("dd/MM/yyyy HH:mm:ss"));
                Console.WriteLine("Total Time Spent        : {0}:{1}:{2}", (_endTime - _startTime).Hours,(_endTime - _startTime).Minutes,(_endTime - _startTime).Seconds);
                WriteLineSeparator();
            }
        }

        private static void ShowTFSWIMorphyingUsage()
        {
            Console.WriteLine(string.Empty);
            Console.WriteLine("TFSWIMorphying applicaiton is used for migrating WorkItems data and links from one format to another.\n");
            Console.WriteLine(string.Empty);
            Console.WriteLine("Usage");
            Console.WriteLine("=====");
            Console.WriteLine(string.Empty);
            Console.WriteLine("TFSWIMorphying.exe --TFSWIMorphyingConfig=[TFS WorkItems Morphying Application Configuration File Path]");
            Console.WriteLine(string.Empty);
            Console.WriteLine("    --TFSWIMorphyingConfig : Parameter should points to an XML file that comply to TFSWIMorphyingConfig.xsd.");
            Console.WriteLine("                             You will find an example configuraiton file named \"TFSWIMorphyingConfig.xml\" \n" +
                              "                             in the root folder of this application.");
            Console.WriteLine(string.Empty);
        }
        private static void WriteLineSeparator()
        {
            Console.WriteLine(
                "========================================================================================");
        }
    }
}
